﻿using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Extensions.Configuration;

using System.Collections.Concurrent;
using System.Data;
using System.Diagnostics;

using WXWorkSvc.Common.Data;

namespace WXWorkSvc.Common.Models;

public class SqlServerLoggerProcessor : IDisposable
{
    public const string SectionName = "SqlServerLogger";
    public const string KeyAppId = "AppId";
    const string KeyConnectionStringName = "ConnectionStringName";
    public int AppId { get; }
    public string ConnectionString { get; }
    //private readonly SqlCommand _SqlCommand;
    private readonly BlockingCollection<TLogger> _BC = new();

    public SqlServerLoggerProcessor(IConfiguration configuration)
    {
        AppId = configuration.GetSection(SectionName).GetValue<int>(KeyAppId);
        var connectionStringName = configuration.GetSection(SectionName).GetValue<string>(KeyConnectionStringName) ?? throw new InvalidOperationException($"{SectionName}/{KeyConnectionStringName} not found."); ;
        ConnectionString = configuration.GetConnectionString(connectionStringName) ?? throw new InvalidOperationException($"Connection string 'connectionStringName' not found."); ;
        var sqlConnection = new SqlConnection(ConnectionString);
        var sqlCommand = sqlConnection.CreateCommand();
        sqlCommand.CommandText = ConnectionString;
        sqlCommand.CommandText = "PAddLogger";
        sqlCommand.CommandType = CommandType.StoredProcedure;

        SqlParameter tvpParam = sqlCommand.Parameters.Add("@Items", SqlDbType.Structured);
        tvpParam.TypeName = "dbo.LoggerType";


        Task.Run(async () =>
        {
            List<TLogger> loggerList = new List<TLogger>();
            while (_BC.TryTake(out var logger, -1))
            {
                loggerList.Clear();
                logger.AppId = AppId;
                loggerList.Add(logger);
                while (_BC.TryTake(out logger))
                {
                    logger.AppId = AppId;
                    loggerList.Add(logger);
                }

                sqlCommand.Parameters[0].Value = CreateSqlDataRecords(loggerList);

                try
                {
                    await sqlConnection.OpenAsync();
                    await sqlCommand.ExecuteNonQueryAsync();
                }
                catch (Exception)
                {
                }
                finally
                {
                    sqlConnection.Close();
                }
            }
        });

    }
    public void Write<TState>(string categoryName, LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        _BC.Add(new TLogger(categoryName, logLevel, formatter(state, exception), exception));
    }

    private IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<TLogger> items)
    {
        SqlMetaData[] metaData = new SqlMetaData[5];

        metaData[0] = new SqlMetaData("AppId", SqlDbType.Int);
        metaData[1] = new SqlMetaData("Dt", SqlDbType.DateTime2);
        metaData[2] = new SqlMetaData("Name", SqlDbType.NVarChar, 450);
        metaData[3] = new SqlMetaData("LogLevel", SqlDbType.Int);
        metaData[4] = new SqlMetaData("Msg", SqlDbType.NVarChar, SqlMetaData.Max);
        SqlDataRecord record = new SqlDataRecord(metaData);
        foreach (var item in items)
        {
            record.SetInt32(0, item.AppId);
            record.SetDateTime(1, item.Dt);
            record.SetString(2, item.Name);
            record.SetInt32(3, (int)item.LogLevel);
            record.SetString(4, item.Msg);
            yield return record;
        }
    }

    bool disposed = false;
    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                _BC.CompleteAdding();
                while (!_BC.IsCompleted)
                    Thread.Sleep(200);
                _BC.Dispose();
            }

            disposed = true;
        }
    }


}
